// ============================================================================
// JigGraphics
// One possible graphical representation of the game. The graphics
// know about a JigClient but the JiGClient knows nothing about the graphics
// this would allow a different graphical front end to be written without
// affecting the gaming engine.
//
// CLH April-June 1998
// ============================================================================

package com.chrishobson.jiggle;

import java.awt.event.*;
import java.awt.*;

public class JigGraphics extends Thread {

  // The static constants for the graphics border and
  // the size of the individual game squares in pixels
  static public final int s_border_width = 2;
  static public final int s_square_size = 16;

  // ==========================================================================
  // Constructor needs the JigClient and needs to know how big to make
  // the graphics area.
  // ==========================================================================
  public JigGraphics(JigClient client, int x, int y) {
    m_x = x;
    m_y = y;
    m_client = client;
    m_frame = new JigGraphicalFrame(this, x, y);
  }

  // ==========================================================================
  // The main run loop. Just loops forever redrawing as required
  // ==========================================================================
  public void run() {
    m_frame.update_graphics();
    // The graphics just loops forever because the JigClient will
    // exit the program when the user quits or when the server
    // connection is broken
    while (true) {
      int update_count = m_client.get_update_count();
      if (update_count > m_last_update) {
        m_frame.update_graphics();
        m_last_update = update_count;
        // Tell client that we have handled this update and so
        // it can accept the next command
        m_client.handled();
      }
      yield();
    }
  }

  // ==========================================================================
  // Simple wrappers which pass messages onto the JigClient. Made final to
  // allow the compile to inline them
  // ==========================================================================
  public final void left_move() {
    m_client.left_move();
  }

  public final void right_move() {
    m_client.right_move();
  }

  public final void quit_game() {
    m_client.quit_game();
  }

  public final void join_game() {
    m_client.join_game();
  }

  public final JigVector get_worms() {
    return m_client.get_worms();
  }

  public final int num_food() {
    return m_client.num_food();
  }

  public final int[][] get_food() {
    return m_client.get_food();
  }

  public final int num_block() {
    return m_client.num_block();
  }

  public final int[][] get_block() {
    return m_client.get_block();
  }

  public final int get_id() {
    return m_client.get_id();
  }

  // ==========================================================================
  // The member data
  // ==========================================================================
  private JigGraphicalFrame m_frame;
  private JigClient m_client;
  public int m_x;
  public int m_y;
  private int m_last_update;
}

// ============================================================================
// JigGraphicalFrame
// The graphical frame is used by the JigGraphic, it is the frame which
// actually handles the drawing, via a canvas within it.
//
// CLH April-June 1998
// ============================================================================
class JigGraphicalFrame extends Frame {

  // ==========================================================================
  // Creates the frame and its nested canvas. The canvas handles the
  // key strokes and uses the new Java1.1 event model.
  // ==========================================================================
  JigGraphicalFrame(JigGraphics graphics, int x, int y) {
    super("Jiggle - By Chris Hobson");
    setBackground(Color.black);
    // Compute the exact size for the canvas
    int c_x = x * JigGraphics.s_square_size + 2 * JigGraphics.s_border_width;
    int c_y = y * JigGraphics.s_square_size + 2 * JigGraphics.s_border_width;
    // Make our self bigger to hold it
    setSize(c_x + 10, c_y + 35);
    add(m_canvas = new JigGraphicalCanvas(graphics, c_x, c_y));
    m_canvas.doLayout();
    // show();
    setVisible(true);
  }

  // ==========================================================================
  // Forces a redraw and waits until it has occurred.
  // ==========================================================================
  public void update_graphics() {
    m_canvas.m_painted = false;
    m_canvas.repaint();
    //
    // And wait until the canvas confirms that the screen
    // has been updated. If we don't do this then the game and
    // screen can become unsyncronised.
    while (m_canvas.m_painted == false) {
      // Really I just wanted to yield but on zappa I
      // have to sleep to allow the graphics to be updated
      // I don't know why maybe suns virtual machine schedules
      // threads in a different way. Most annoying! it works
      // fine with a yield on NT and Win95
      // Also 5 seems to be the magic number, sleep for less than
      // 5 milliseconds and paint is not called. (Strange said Alice)
      try {
        Thread.currentThread();
        // Should I suspsend instead!!!!!!!! ?
        Thread.sleep(5);
        if (m_canvas.m_painted == false) {
          m_canvas.repaint();
        }
      } catch (Exception e) {
      }
    }

  }

  private JigGraphicalCanvas m_canvas;
}

// ============================================================================
// JigGraphicalCanvas
// The canvas *really* does the drawing and keyboard handling
//
// CLH April-June 1998
// ============================================================================
class JigGraphicalCanvas extends Canvas {
  // Public painted flag to allow the frame to
  // syncronise when the repaint has occured.
  public boolean m_painted;

  // ==========================================================================
  // Creates a canvas of the correct size and attaches a keyboard listener
  // to it. This uses the Java1.1 event model.
  // ==========================================================================
  JigGraphicalCanvas(JigGraphics graphics, int x, int y) {
    m_graphics = graphics;
    addKeyListener(new JigGraphicsKey(graphics));

    m_bx = x - 1;
    m_by = y - 1;
    setSize(x, y);
  }

  // ==========================================================================
  // Paint is very simple. If the offscreen image exists draw it to the screen
  // otherwise do nothing.
  // ==========================================================================
  public void paint(Graphics g) {
    if (m_buf != null) {
      // Paints only role is to flash up the off screen
      // image if it hase been created. This ensures that
      // expose events automatically update the screen
      g.drawImage(m_buf, 0, 0, this);
    }
    m_painted = true;
  }

  // ==========================================================================
  // Update is where the offscreen image is created (first time only) and
  // where the image is updated for the movements etc.
  // ==========================================================================
  public void update(Graphics g) {
    // First call, create offscreen image
    if (m_buf == null) {
      m_buf = createImage(m_bx + 1, m_by + 1);
      m_graphics_context = m_buf.getGraphics();
      // Draw a red border into the image
      m_graphics_context.setColor(m_back);
      m_graphics_context.fillRect(0, 0, m_bx + 1, m_by + 1);
      m_graphics_context.setColor(Color.red);
      m_graphics_context.drawRect(0, 0, m_bx, m_by);
      m_graphics_context.drawRect(1, 1, m_bx - 2, m_by - 2);
      // Load the food gif and scale it. The bitmaps on disc are stored
      // as 32x32 pixel gifs. They are scaled to suite the play square size
      m_food_gif = Toolkit.getDefaultToolkit().getImage("res/food.gif");
      m_food_gif = m_food_gif.getScaledInstance(JigGraphics.s_square_size,
          JigGraphics.s_square_size, Image.SCALE_DEFAULT);
      m_block_gif = Toolkit.getDefaultToolkit().getImage("res/block.gif");
      m_block_gif = m_block_gif.getScaledInstance(JigGraphics.s_square_size,
          JigGraphics.s_square_size, Image.SCALE_DEFAULT);
      //
      // We want to wait until the images are loaded and scaled
      // Otherwise we can't get on and draw them now. If we don't wait
      // the off screen bitmap is not correct at the start of the game
      // The standard MediaTracker class does all of this for us.
      MediaTracker track = new MediaTracker(this);
      track.addImage(m_food_gif, 1);
      track.addImage(m_block_gif, 1);
      try {
        track.waitForID(1);
      } catch (Exception e) {
      }

      // Add blocks as a once off activity, they can never get
      // overdrawn once they are in the image.
      int num_block = m_graphics.num_block();
      int[][] block = m_graphics.get_block();
      while (num_block-- > 0) {
        // Draw each block at the correct location in the image
        int x = JigGraphics.s_border_width + block[num_block][0]
            * JigGraphics.s_square_size;
        int y = JigGraphics.s_border_width + block[num_block][1]
            * JigGraphics.s_square_size;
        m_graphics_context.drawImage(m_block_gif, x, y, this);
      }
    }
    // Fill the off screen image via its graphical context
    do_paint(m_graphics_context);
    paint(g);
    m_painted = true;
  }

  // ==========================================================================
  // The method update the drawing of all of the worms, and any food which
  // might have appeared.
  // ==========================================================================
  public void do_paint(Graphics g) {

    // Update worms
    JigVector worms = m_graphics.get_worms();
    int num = worms.size();
    int my_id = m_graphics.get_id();
    for (int id = 0; id < num; ++id) {
      JigWorm worm = (JigWorm) worms.elementAt(id);
      if (worm != null) {
        // The players worm is drawn slightly differently so you know
        // which is your worm.
        boolean me = (my_id == id ? true : false);
        worm.draw(g, m_back, JigGraphics.s_border_width,
            JigGraphics.s_square_size, me);
        if (worm.is_dead() == true) {
          // It has undraw itself so remove it from the array
          worms.set_null(id);
        }
      }
    }

    // Any new food ?
    int num_food = m_graphics.num_food();
    if (num_food > 0) {
      int[][] food = m_graphics.get_food();
      while (num_food-- > 0) {
        // Draw the food into the image
        int x = JigGraphics.s_border_width + food[num_food][0]
            * JigGraphics.s_square_size;
        int y = JigGraphics.s_border_width + food[num_food][1]
            * JigGraphics.s_square_size;
        g.drawImage(m_food_gif, x, y, this);
      }
    }
  }

  // ==========================================================================
  // The member data
  // ==========================================================================
  private JigGraphics m_graphics;
  public Image m_buf;
  private Image m_food_gif;
  private Image m_block_gif;
  private Graphics m_graphics_context;
  private int m_bx;
  private int m_by;
  private final Color m_back = Color.gray;
}

// ============================================================================
// JigGraphicsKey
// Simple interface class which listens to the users key strokes.
//
// CLH April-June 1998
// ============================================================================
class JigGraphicsKey implements KeyListener {
  public JigGraphicsKey(JigGraphics graphics) {
    m_graphics = graphics;
  }

  // We don't care about these events but must implement the methods
  public void keyTyped(KeyEvent e) {
  }

  public void keyReleased(KeyEvent e) {
  }

  // ==========================================================================
  // The user pressed a key. Single the event to the graphics so they can
  // pass it on to the client.
  // ==========================================================================
  public void keyPressed(KeyEvent e) {
    switch (e.getKeyCode()) {
    case KeyEvent.VK_LEFT: {
      m_graphics.left_move();
      break;
    }
    case KeyEvent.VK_RIGHT: {
      m_graphics.right_move();
      break;
    }
    case KeyEvent.VK_J: {
      m_graphics.join_game();
      break;
    }
    case KeyEvent.VK_Q: {
      m_graphics.quit_game();
      break;
    }
    // Unkown key. Print a simple help
    default:
      System.out.println("Jiggle Keys\n" + "===========\n"
          + "Left  = Left Arrow\n" + "Right = Right Arrow\n"
          + "J     = Join Game\n" + "Q     = Quit Game\n");
    }
  }

  // The graphics class we need to pass messages to
  private JigGraphics m_graphics;
}
